##------------------------------------------------------------------------------
##
##	yasco-1.0 - Yet Another (Java)Script Obfuscator
##	Copyright (C) 2008-2011 Oliver Schieche <post@oliver-schieche.de>
##
##	Makes use of Yapp
##	Parse::Yapp - Copyright (c) 1998-2001, Francois Desarmenien, all right reserved.
##
##	$Id: CommandLine.pm 12 2012-10-01 13:57:30Z goresplatter1844@gmail.com $
##------------------------------------------------------------------------------
package Yasco::CommandLine;

use strict;
use Carp qw/confess cluck/;
use Pod::Usage;
use Getopt::Long;
use Data::Dumper;

use Yasco::Main;

Getopt::Long::Configure('bundling');
##------------------------------------------------------------------------------
sub run
	{
		replace_envvars();

		my %options = get_options();
		my ($o,$code,$file);

		if ($options{no_encode} && $options{name_encrypt})
			{
				warn '"--name-encrypt|-N" will not work with "--no-encode|-E". Disabling "--name-encrypt"';
				delete $options{name_encrypt};
			}

#		$SIG{__DIE__} = sub {print STDERR @_;exit -1};#\&confess;
#		$SIG{__WARN__} = \&cluck;

		OPEN_STREAM: {
			$file = \*STDIN, last OPEN_STREAM
				unless defined($options{file});
			open $file, '<', $options{file} or die "Error opening $options{file}: $!";
		}

		$code = do {local($/)=undef;<$file>};
		$code = ${[split /^__END__/m, $code]}[0];

		close $file if defined($options{file});

		$o = Yasco::Main->new;
		$o->adopt_options(%options);

		$o->add_filter(qq{Yasco::Filter::Logo}) unless $o->option(qq{no_logo});

		ADD_COMPRESSOR: {
			last ADD_COMPRESSOR unless $o->option(qq{compress});
			$o->add_filter(qq{Yasco::Filter::Compress}),
			last ADD_COMPRESSOR							if 1 == $o->option(qq{compress});
			$o->add_filter(qq{Yasco::Filter::Compress2}),
			last ADD_COMPRESSOR							if 2 == $o->option(qq{compress});
			$o->add_filter(qq{Yasco::Filter::Compress3}),
			last ADD_COMPRESSOR							if 3 == $o->option(qq{compress});
			$o->add_filter(qq{Yasco::Filter::Compress4}),
			last ADD_COMPRESSOR							if 4 == $o->option(qq{compress});
			warn 'No such compressor class: "' . $o->option(qq{compress}) . '"; option ignored.'
		}

		$o->add_filter(qq{Yasco::Filter::Keywords}) if $o->option(qq{keywords});
		$o->add_filter(qq{Yasco::Filter::MapString}) unless $o->option(qq{no_encode});
		$o->add_filter(qq{Yasco::Filter::MapId}) unless $o->option(qq{no_mapping});
		$o->add_filter(qq{Yasco::Filter::HashEncrypt}) if $o->option(qq{hash_encrypt});

		$code = $o->obfuscate($code);

		FLUSH_CODE: {
			print(STDOUT $code), last FLUSH_CODE
				unless defined($options{output});
			open FILE, '>', $options{output} or die "Can't open $options{output} for writing: $!";
			binmode FILE;
			print FILE $code;
			close FILE;
		}
	}
##------------------------------------------------------------------------------
sub replace_envvars()
	{
		foreach (@ARGV) {
			next unless m=(#\{(\w+)\})=;
			my $rep = qr/$1/;
			my $var = $ENV{$2} or die "No such environment variable: \"$2\"";

			s/$rep/$var/g;
		}
	}
##------------------------------------------------------------------------------
sub usage;
sub get_options
	{
		my %options = (
			newlines				=> 0,
		);

		GetOptions(
			'a|annotate'			=> \$options{annotate},
			'c|compress+'			=> \$options{compress},
			'C|no-obfuscate'		=> \$options{no_obfuscate},
			'd|debug=s'				=> \$options{debug},
			'D|deobfuscate'			=> \$options{deobfuscate},
			'E|no-encode'			=> \$options{no_encode},
			'f|file=s'				=> \$options{file},
			'h|help'				=> \&Yasco::CommandLine::usage,
			'H|hash-encrypt'		=> \$options{hash_encrypt},
			'i|inline-threshold=i'	=> \$options{inline_threshold},
			'I|no-auto-insert'		=> \$options{no_asi},
			'k|keywords!'			=> \$options{keywords},
			'L|no-logo'				=> \$options{no_logo},
			'M|no-mapping'			=> \$options{no_mapping},
			'm|map-from-scope=i'	=> \$options{map_scope_start},
			'n|newlines=s'			=> \$options{newlines},
			'N|name-encrypt+'		=> \$options{name_encrypt},
			'no-compression'		=> \$options{no_compression},
			'o|output=s'			=> \$options{output},
			'O|option=s%'			=> \$options{filter_options},
			'optimize!'				=> \$options{optimize},
			'q|quiet'				=> \$options{quiet},
			'w|keep-whitespace'		=> \$options{white_space},
			'W|no-warnings'			=> \$options{no_warnings}
		) or Yasco::CommandLine::usage(1);

		delete $options{compress} if $options{no_compression};

		%options
	}
##------------------------------------------------------------------------------
sub usage
	{
		my $v = shift || 0;

		pod2usage(
			-exitval => -1,
			-verbose => ($v ? 0 : 99),
			-sections => "NAME|SYNOPSIS|OPTIONS AND ARGUMENTS",
			-input => __FILE__,
			-noperldoc => 1
		)
	}
##------------------------------------------------------------------------------
1;
##------------------------------------------------------------------------------
=pod

=head1 NAME

yasco - Yet Another (Java)Script Obfuscator

=head1 SYNOPSIS

=over 8

B<yasco.pl>

[B<-a|--annotate>]
[B<-c|--compress>]
[B<-C|--no-obfuscate>]
[B<-d|--debug> I<value>]
[B<-D|--deobfuscate>
[B<-E|--no-encode>]
[B<-f|--file> I<value>]
[B<-h|--help>]
[B<-H|--hash-encrypt>]
[B<-I|--no-auto-insert>]
[B<-k|--keywords>]
[B<-L|--no-logo>]
[B<-m|--map-from-scope> I<value>]
[B<-M|--no-mapping>]
[B<-n|--newlines> I<value>]
[B<-N|--name-encrypt>]
[B<-o|--output> I<value>]
[B<-O|--option> I<value>]
[B<--optimize>
[B<-q|--quiet>]
[B<-w|--keep-whitespace>]

=back

=head1 DESCRIPTION

yasco aims to be a simple JavaScript obfuscator.  With default behavior, yasco first parses the provided input file and
stores the entire script in a node-tree (parsing stage).  With a few exceptions, the parser ignores comments and whitespace.

In obfuscation stage, the node tree is reassembled into valid JavaScript.  For obfuscation options see the
appropriate section in this document.

=head1 OPTIONS AND ARGUMENTS

=over 8

=item B<-a, --annotate>

For debugging purposes. In obfuscation stage, reconstructed nodes will prepend
their resulting code with a comment used for debugging.

=item B<-c, --compress>

Will compress the final obfuscation result. Can be stated multiple times.

=item B<-C, --no-obfuscate>

Syntax check only. Does nothing with the source.

=item B<-d, --debug> I<value>

Print debugging information. Argument is numeric. Values are to be OR'ed together and can include:

	* 0x01 Token reading
	* 0x02 States information
	* 0x04 Driver actions (shifts, reduces, accept...)
	* 0x08 Parse Stack dump
	* 0x10 Error Recovery tracing
	* 0x20 Identifier mapping (obfuscate stage)
	* 0x40 General information

=item B<-D, --deobfuscate>

Makes parsed code readable again. It will however not reverse compression or name substitution.

=item B<-E, --no-encode>

Do not encode strings found in source.  In obfuscation stage all strings found would normally be put in a hash with
the "encodee" being the key.  Thus multiple instances of C<'foo'> will only be referenced once.  During reassembly
that string will be rewritten to point to an entry in the global C<_osV>-String table.

=item B<-f, --file> I<filename>

Uses "filename" as input.  Yasco reads from stdin if no filename is given.

=item B<-h, --help>

Duh.

=item B<-H, --hash-encrypt>

Like the name says, encrypts hashes. Useful for masking a hash's keys.

	{foo:bar,1:2,blargh:"zoid"}

will become

	(function(){var l12={};l12[_osVxx.s_1]=bar;l12[1]=2;l12[_osVxx.s_2]=_osVxx.s_3;return l12;})()

=item B<-I, --no-auto-insert>

Disable automatic semicolon insertion. Use in conjunction with B<--no-obfuscate (-C)> for vigorous syntax checking. ;-)

=item B<-k, --keywords>

Enable keyword substitution. Commonly used objects and methods are aliased. The following keywords are subject to aliasing:

	* addClassName
	* charCodeAt
	* clearTimeout
	* document
	* elements
	* extend
	* fromCharCode
	* hasClassName
	* indexOf
	* isUndefined
	* length
	* location
	* match
	* onbeforeunload
	* onclick
	* onsubmit
	* navigator
	* Prototype
	* prototype
	* removeClassName
	* setTimeout
	* String
	* substr
	* substring
	* userAgent
	* window

The following options can be passed to the filter:

	* always_replace
	* verbose
	* wrapper_object

By default, the objects are aliased to global variables sequentially named starting with a capital letter A. When using the
option "wrapper_object", a global object is used for aliasing instead:

	yasco.pl ... --keywords -O Yasco::Filter::Keywords=verbose:1,wrapper_object=1

This will modify the following code

	var a = document.location.href,
		b = String.fromCharCode(64);

to look like this:

	var Be={D:String,A:document,B:"location",C:"href",E:"fromCharCode"};
	var v0=Be.A[Be.B][Be.C],
		v1=Be.D[Be.E](64);

Actually, for this example to work, yasco needs the "always_replace" option set to one. Aliasing will only be done for objects
referenced more than once.

=item B<-L, --no-logo>

Don't prefix outputted code with the Yasco banner.

=item B<-M, --no-mapping>

Do not substitute identifier names.  By default Yasco would rename any B<declared> variables.

	var foo = 2, bar = "zoid", frob = slursh;

The snippet above would be rewritten as:

	var v0=2,v1=_osVxx.s_1,v2=slursh;

Notice that C<slursh> has not been renamed, as it wasn't declared using C<var>. This behavior might be
desirable for globally accessible "classes" (see B<--map-from-scope (-m)>).

=item B<-m, --map-from-scope> I<value>

Only map identifiers that are I<value> scopes deep. Scope #1 would be the script, anything deeper (say, in a
function declaration) will be a scope level higher.

This might be useful if you have globally declared (i.e. used outside the Javascript file) functions or
objects, that you don't wish to be id-mapped.

=item B<-n, --newlines> I<value>

Inserts extra newline whitespaces where desired.  This option is mainly a debugging kind of option, since the
generated whitespace merely improves readability of obfuscated code (read: are superfluous ones).

I<value> should be a numeric value glued via OR from:

	* 0x01 newline after ';' ("one statement per line")
	* 0x02 newline after scope-closing brace '}'
	* 0x04 newline after `ASSIGN` operations
	* 0x08 newline after `RETURN` statements
	* 0x10 newline after labels

=item B<-N, --name-encrypt>

Will encrypt object member names.  Access to an object's member via the C<.> operator would be rewritten to an
access via the C<[]> operators and its name stringified, so it can in turn be used with string encoding:

	document.location.href = "http://perfect-co.de"

Will transform into:

	document[_osVxx.s_1][_osVxx.s_2]=_osVxx.s_3

=item B<-o, --output> I<value>

Save obfuscated script to file I<value>. Yasco spills guts to stdout if this option is omitted.

=item B<-O, --option> C<value(s)>

Pass options to plug-ins.

=item B<-q, --quiet>

Be silent.

=item B<--optimize>

For now, this option enables optimization of "if(1)..." statements.

=item B<-w, --keep-whitespace>

Thought to be used in conjunction with the B<--deobfuscate (-D)> option, this one is actually a
semi-working feature.

=back

=head1 BUGS

=item Regular Expressions

=over 8

Since regular expressions are one of the design flaws of ECMAScript, it is hard to detect, whether
"4+5/4-a+1/f" actually is an arithmetic expression or a reg-ex.

For now, the lexer looks back one token and returns "reg-ex" when the token was neither an identifier,
a number nor a function call.


=back

=head1 SUGGESTIONS

...will duely be noted.

=head1 AUTHOR

Oliver Schieche <post@oliver-schieche.de>

=head1 COPYRIGHT

The Yasco module and its related shell script are copyright
(C) 2008-2012 by Oliver Schieche. All rights reserved.

You may use and distribute them under the terms of either
the GNU General Public License or the Artistic License,
as specified in the Perl README file.

This is the copyright message from Parse::Yapp. As requested: typos left untouched ;-)

=over 8

The Parse::Yapp module and its related modules and shell scripts are copyright
(c) 1998-2001 Francois Desarmenien, France. All rights reserved.

You may use and distribute them under the terms of either
the GNU General Public License or the Artistic License,
as specified in the Perl README file.

If you use the "standalone parser" option so people don't need to install
Parse::Yapp on their systems in order to run you software, this copyright
noticed should be included in your software copyright too, and the copyright
notice in the embedded driver should be left untouched.

=back

=head1 ACKNOWLEDGEMENTS

Kudos to Mr. Desarmenien for writing the Parse::Yapp module.  Thank you.

=cut